//
// class that manager geometric entities to compose a complete frame
//////////////////////////////////////////////////////////////////////////

#include "acmframe.h"
#include "geometry/curves/curve.h"
#include "attribute/att.h"
#include <string.h>

//
//////////////////////////////////////////////////////////////////////////
acmFrame::acmFrame( )
{
  m_objdraw = NULL;
}

//
//////////////////////////////////////////////////////////////////////////
acmFrame::~acmFrame()
{
  clear();
}

//
//////////////////////////////////////////////////////////////////////////
bool acmFrame::read( char *file_name )
{
  FILE *f = fopen (file_name, "r");


  if (f == NULL) 
    return false;

  // clear all entities
  clear ();

  if (!readGeometry (f)) 
    return 0; 

  fclose (f);

  return 1;
}

//
//////////////////////////////////////////////////////////////////////////
bool acmFrame::write( char *name )
{

  return true;
}

//
//////////////////////////////////////////////////////////////////////////
void acmFrame::setDraw( drawGRA *objdraw )
{
  m_objdraw = objdraw;
}

//
//////////////////////////////////////////////////////////////////////////
void acmFrame::addCurve (Curve *cv)
{
  Curve *new_cv;
  cv->copy(&new_cv);
  m_curves.push_back(new_cv);

  // set attribute
  EdgeAtt *attr = new EdgeAtt;
  new_cv->setAttribute(attr);
}

//
//////////////////////////////////////////////////////////////////////////
bool acmFrame::readGeometry( FILE *f )
{
  char label[80];
  rewind (f);

  do
  {
    if (nextLabel (f, label) == 0)
      return 0;

    if (strcmp (label, "VERTEX") == 0)
    {
      readVertex (f);
    }
    if (strcmp (label, "CURVE.LINE") == 0)
    {
      readCurve (f, Curve::LINE);
    }
    else if (strcmp (label, "CURVE.POLYLINE") == 0)
    {
      readCurve (f, Curve::POLYLINE);
    }
    else if (strcmp (label, "CURVE.ARC") == 0)
    {
      readCurve (f, Curve::ARC);
    }
    else if (strcmp (label, "CURVE.SPLINE") == 0)
    {
      readCurve (f, Curve::SPLINE);
    }
    else if (strcmp (label, "GLOBAL.PARAMETERS") == 0)
    {
      readGlobalParameters (f);
    }

  } while ( strcmp ( label, "END" ) != 0 );
 
  return true;
}

/* ======================  NF_ReadAttribute  ======================== */
void acmFrame::readCurveAttr (FILE *ptr_file)
{
  char    label[80];
  Curve   *curr_curve = m_curves[m_curves.size()-1];
  EdgeAtt *curr_attr = NULL;
  curr_curve->getAttribute((void **) &curr_attr);

  // check for attribute
  if (curr_attr == NULL)
    curr_attr = new EdgeAtt;
  curr_curve->setAttribute(curr_attr);

  // get curve id and attributes
  if (findCurve (ptr_file))
  {
    while( 1 )
    {
      if( nextAttribute (ptr_file, label) == 0 )
        return;
      else if( strcmp( label, "NUM_SUBDV" ) == 0 )
      {
        fscanf( ptr_file, "%d", &curr_attr->Subdivision.num_sub );
      }
      else if( strcmp( label, "RATIO" ) == 0 )
      {
        fscanf( ptr_file, "%lf", &curr_attr->Subdivision.ratio );
      }
      else if( strcmp( label, "CURVE_UNIFORM_FORCE" ) == 0 )
      {
        curr_attr->obj_att = acmF_Attribute::CreateAttribute (CURVE_UNIFORM_FORCE);
        curr_attr->obj_att->Read (ptr_file);
      }
      else if( strcmp( label, "CURVE_SUPPORT" ) == 0 )
      {
        curr_attr->obj_att = acmF_Attribute::CreateAttribute (CURVE_SUPPORT);
        curr_attr->obj_att->Read (ptr_file);
      }
#if 0
      else if( strcmp( label, "NODAL_FORCE" ) == 0 )
      {
        curr_attr->obj_att = acmF_Attribute::CreateAttribute (NODAL_FORCE);
        curr_attr->obj_att->Read (ptr_file);
      }
      else if( strcmp( label, "NODAL_SUPPORT" ) == 0 )
      {
        curr_attr->obj_att = acmF_Attribute::CreateAttribute (NODAL_SUPPORT);
        curr_attr->obj_att->Read (ptr_file);
      }
#endif
      else if(strcmp (label, "'") == 0)
        break;

      else          // not a known label
        continue;
    }
  }

}

/* ======================  NF_ReadAttribute  ======================== */
void acmFrame::readVertexAttr (FILE *ptr_file)
{
  char    label[80];
  vertex   *curr_vert = m_vertices[m_vertices.size()-1];
  VertAtt *curr_attr = NULL;
  curr_vert->getAttribute((void **) &curr_attr);

  // check for attribute
  if (curr_attr == NULL)
    curr_attr = new VertAtt;
  curr_vert->setAttribute(curr_attr);

  // get curve id and attributes
  if (findCurve (ptr_file))
  {
    while( 1 )
    {
      if( nextAttribute (ptr_file, label) == 0 )
        return;
      else if( strcmp( label, "NODAL_FORCE" ) == 0 )
      {
        curr_attr->obj_att = acmF_Attribute::CreateAttribute (NODAL_FORCE);
        curr_attr->obj_att->Read (ptr_file);
      }
      else if( strcmp( label, "NODAL_SUPPORT" ) == 0 )
      {
        curr_attr->obj_att = acmF_Attribute::CreateAttribute (NODAL_SUPPORT);
        curr_attr->obj_att->Read (ptr_file);
      }
      else if(strcmp (label, "'") == 0)
        break;

      else          // not a known label
        continue;
    }
  }

}


// This function find the next label in the file. The label is identified by the
// character %.
// Parameters: f     -> input file pointer.
//             label -> output string.
//
// Ex: %CURVE.LINE
//
// The function returns label[] = "CURVE.LINE", without %.
// Returns the value 1 if sucess and 0 if error.
//////////////////////////////////////////////////////////////////////////
int acmFrame::nextLabel ( FILE *f,  char *label )
{
  int c;

  while ((c = fgetc(f)) != '%')         /* finds next % */
  {
    if (c == EOF)
      return 0;
  }
  if (fscanf (f, "%s", label) != 1)     /* scan label string */
    return 0;
  else
    return 1;

}  /* End of NextLabel */


// FindCurve
// This function finds a (') or end of line. If a (') is founded returns 1,
// else if a end of line is founded returns 0.
//////////////////////////////////////////////////////////////////////
int acmFrame::findCurve (FILE *fp)
{
  int	c;

  do
  {
    c = fgetc( fp );
    //  if( c == '\n' ) return 0;
  } while( c != '\'' );
  return 1;
}

// NextAttribute
//////////////////////////////////////////////////////////////////////
int acmFrame::nextAttribute(FILE *fp, char *label )
{
  // return the attribute label readen from a primitive curve attribute file */
  if( fscanf(fp, "%s", label ) != 1 )
    return 0;
  else
  {
    return 1;
  }

}

//
//////////////////////////////////////////////////////////////////////////
int acmFrame::readCurve( FILE *f, Curve::ECurveType _type )
{
  Curve *new_curve = CurveFactory::create(_type);
  double x, y, z;

  // get number of points
  int n;
  fscanf(f, "%d", &n);
  if (n == 0)
    return 0;

  if (_type == Curve::ARC)
    new_curve->beginAddPoint(0.0, 0.0, 1.0);
  else
    new_curve->beginAddPoint(0.0, 0.0, 0.0);

  // read additional points
  for (int i = 0; i < n; i++)
  {
    fscanf(f, "%lf %lf %lf", &x, &y, &z);
    new_curve->addPoint(x, y, z);
  }
  new_curve->endAddPoint();

  m_curves.push_back(new_curve);

  // read attribute
  readCurveAttr(f);

  return 1;
}

//
//////////////////////////////////////////////////////////////////////////
int acmFrame::readVertex( FILE *f )
{
  double x, y, z;

  fscanf(f, "%lf %lf %lf", &x, &y, &z);

  vertex *cpy = new vertex;
  cpy->point.setXYZ(x, y, z);
  m_vertices.push_back(cpy);

  readVertexAttr(f);

  return 1;
}

//
//////////////////////////////////////////////////////////////////////////
int acmFrame::readGlobalParameters( FILE *f )
{

  return 1;
}

//
//////////////////////////////////////////////////////////////////////////
void acmFrame::clear()
{
  m_curves.clear();
}

//
//////////////////////////////////////////////////////////////////////////
void acmFrame::draw()
{

  // draw curves
  drawCurves();

  // draw vertices's
  drawVertices();

}

//
//////////////////////////////////////////////////////////////////////////
void acmFrame::getMaxMin( double *xmin, double *xmax, double *ymin, double *ymax )
{
  double xmn, ymn, zmn, xmx, ymx, zmx;
  Box3D box;
  box.clear();

  int n = (int) m_curves.size();
  for (int i = 0; i < n; ++i)
  {
    m_curves[i]->getBox(xmn, ymn, zmn, xmx, ymx, zmx);
    box.update(xmn, ymn, zmn);
    box.update(xmx, ymx, zmx);
  }

  n = (int) m_vertices.size();
  for (int i = 0; i < n; ++i)
  {
    box.update(m_vertices[i]->point.getX(), m_vertices[i]->point.getY(), 0.0);
  }

  if (n == 0)
  {
    *xmin =  0.0;
    *xmax = 10.0;
    *ymin =  0.0;
    *ymax = 10.0;
  }
  else
  {
    box.get(xmn, ymn, zmn, xmx, ymx, zmx);
    *xmin = xmn;
    *xmax = xmx;
    *ymin = ymn;
    *ymax = ymx;
  }

}

//
//////////////////////////////////////////////////////////////////////////
void acmFrame::drawCurves()
{
  if (m_objdraw == NULL)
    return;

  // draw curves
  int n = (int) m_curves.size();
  for (int i = 0; i < n; ++i)
  {
    EdgeAtt *attr = NULL;
    m_curves[i]->getAttribute ((void **) &attr);

    if (attr != NULL)
    {
      // draw attribute
      if (attr->obj_att != NULL)
      {
        attr->obj_att->setDraw(m_objdraw);
        vector <Point3D> poly = m_curves[i]->getPolygonalPoints();
        int npts = poly.size();
        double *pts = new double[npts*2];
        for (int k = 0; k < npts; ++k)
        {
          pts[k*2+0] = poly[k].getX();
          pts[k*2+1] = poly[k].getY();
        }

        m_objdraw->SetLineColor(draw_DARK_MAGENTA);

        attr->obj_att->Draw(npts, pts);

        delete []pts;
      }

      // set color to selected curve
      if (attr->select == 1)
        m_objdraw->SetLineColor(draw_RED);
      else
        m_objdraw->SetLineColor(draw_DARK_BLUE);
    }
    else
      m_objdraw->SetLineColor(draw_DARK_BLUE);

    m_curves[i]->setDraw(m_objdraw);
    m_curves[i]->draw();
  }
}

//
//////////////////////////////////////////////////////////////////////////
void acmFrame::drawVertices()
{
  if (m_objdraw == NULL)
    return;

  // draw curves
  int n = (int) m_vertices.size();
  for (int i = 0; i < n; ++i)
  {
    double pts[2] = {m_vertices[i]->point.getX(), m_vertices[i]->point.getY()};

    VertAtt *attr = NULL;
    m_vertices[i]->getAttribute ((void **) &attr);

    if (attr != NULL)
    {
      // draw attribute
      if (attr->obj_att != NULL)
      {
        attr->obj_att->setDraw(m_objdraw);

        m_objdraw->SetLineColor(draw_DARK_MAGENTA);

        attr->obj_att->Draw(1, pts);
      }

      // set color to selected vertex
      if (attr->select == 1)
        m_objdraw->SetMarkColor(draw_RED);
      else
        m_objdraw->SetMarkColor(draw_DARK_BLUE);
    }
    else
      m_objdraw->SetMarkColor(draw_DARK_BLUE);

    m_objdraw->SetMarkType(drawGRA::draw_X);
    m_objdraw->PolyMark(1, pts);
  }

}

//
//////////////////////////////////////////////////////////////////////////
bool acmFrame::pick( double x, double y, bool add, double tol )
{
  int n;

  // clear all selection
  if (!add)
  {
    n = (int) m_curves.size();
    for (int i = 0; i < n; ++i)
    {
      EdgeAtt *attr = NULL;
      m_curves[i]->getAttribute ((void **) &attr);
      if (attr != NULL)
        attr->select = 0;
    }

    n = (int) m_vertices.size();
    for (int i = 0; i < n; ++i)
    {
      VertAtt *attr = NULL;
      m_vertices[i]->getAttribute ((void **) &attr);
      if (attr != NULL)
        attr->select = 0;
    }
  }

  bool pick_entity = false;

  // check curves
  n = (int) m_curves.size();
  for (int i = 0; i < n; ++i)
  {
    EdgeAtt *attr = NULL;
    m_curves[i]->getAttribute ((void **) &attr);
    if (attr != NULL)
    {
      vector <Point3D> poly = m_curves[i]->getPolygonalPoints();
      bool pick = pickPolyline(poly, x, y, tol);
      if (pick)
      {
        pick_entity = true;
        attr->select = !attr->select;
      }
    }
  }

  n = (int) m_vertices.size();
  double xmin, xmax, ymin, ymax, xv, yv;
  // define box
  xmin = x - tol;
  xmax = x + tol;
  ymin = y - tol;
  ymax = y + tol;
  for (int i = 0; i < n; ++i)
  {
    VertAtt *attr = NULL;
    m_vertices[i]->getAttribute ((void **) &attr);
    if (attr != NULL)
    {
      xv = m_vertices[i]->point.getX();
      yv = m_vertices[i]->point.getY();
      if (xv > xmin && xv < xmax && yv > ymin && yv < ymax)
      {
        pick_entity = true;
        attr->select = !attr->select;
      }
    }
  }

  return pick_entity;
}

//
//////////////////////////////////////////////////////////////////////////
void 
acmFrame::pickCode( double x, double y, double xmin, double xmax, 
                         double ymin, double ymax, int code[4] )
{
  code[0] = x < xmin;
  code[1] = x > xmax;
  code[2] = y < ymin;
  code[3] = y > ymax;
}

//
//////////////////////////////////////////////////////////////////////////
bool 
acmFrame::pickPolyline( vector <Point3D> &poly, double x, double y, double tol )
{
  int    i, j;
  int    cod0[4], cod1[4];
  double x0, y0, x1, y1;
  double xmin, xmax, ymin, ymax;

  // define box
  xmin = x - tol;
  xmax = x + tol;
  ymin = y - tol;
  ymax = y + tol;

  // check each segment
  int n = poly.size();
  for (i = 1; i < n; ++i)
  {
    x0 = poly[i-1].getX();
    x1 = poly[i].getX();
    y0 = poly[i-1].getY();
    y1 = poly[i].getY();

    pickCode(x1, y1, xmin, xmax, ymin, ymax, cod1);
    do 
    {
      pickCode(x0, y0, xmin, xmax, ymin, ymax, cod0);

      // test no-trivial pick
      for (j = 0; j < 4; ++j)
      {
        if (cod0[j] && cod1[j])
          break;
      }
      if (j != 4)
        break;

      // move point to window limit
      if (cod0[0])
        y0 += (xmin - x0) * (y1 - y0) / (x1 - x0), x0 = xmin;
      else if (cod0[1])
        y0 += (xmax - x0) * (y1 - y0) / (x1 - x0), x0 = xmax;
      else if (cod0[2])
        x0 += (ymin - y0) * (x1 - x0) / (y1 - y0), y0 = ymin;
      else if (cod0[3])
        x0 += (ymax - y0) * (x1 - x0) / (y1 - y0), y0 = ymax;
      else
        return true;

    }while (1);
  }

  return false;
}

